package org.jlambda.util;

import static org.jlambda.Context._;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jlambda.exceptions.ApplyException;
import org.jlambda.functions.Fun1;
import org.jlambda.functions.Fun2;
import org.jlambda.functions.Fun3;
import org.jlambda.list.List;
import org.jlambda.listFunctions.ListFun1;
import org.jlambda.listFunctions.ListFun2;

final public class Text {
	
	public static String NewLine = System.getProperty("line.separator");
	
	/**
	 * Reads lines from this reader
	 * 
	 * NOTE this does not close the buffered reader.
	 * 
	 * readLines list BufferedReader
	 * 
	 */
	public static final class ReadLines extends ListFun1<BufferedReader, String>{
		BufferedReader reader = null;

		@Override
		protected void create(List<String> list, BufferedReader p1) {
			reader = p1;
		}

		public String getNext() {
			String s = null;
			try {
				s = reader.readLine();
			} catch (IOException e) {
				// throw new ApplyException("Could not readline ",e);
				s = null;
			}
			
			if (s == null) {
				EOL();
				return null;
			} else return s;
		}
	};	
	
	public static final ListFun1<BufferedReader, String> readLines = new ReadLines();

	public static Pattern linePattern = Pattern.compile(".*\r?\n");
	
	/**
	 * Reads lines from a CharSequence
	 * 
	 * readCSLines CharSequence
	 * 
	 */
	public static final class ReadCSLines extends ListFun1<CharSequence, String>{
		Matcher matcher = null;
		CharSequence seq = null;
		boolean finished = false;

		@Override
		protected void create(List<String> list, CharSequence p1) {
			seq = p1;
			matcher = linePattern.matcher( p1 );
		}

		public String getNext() {
			String s = null;
			if (!finished && matcher.find()) {
				s = matcher.group();
			}
			
			if (matcher.end() == seq.length()) {
				finished = true;  // set it up for the next one
			}
			
			if (s == null) {
				EOL();
				return null;
			} else return s;
		}
	};	

	public static final ListFun1<CharSequence, String> readCSLines = new ReadCSLines();

	
	
	/**
	 * Reads lines from a Scanner
	 * 
	 * readSLines Scanner
	 * 
	 */
	public static final class ReadSLines extends ListFun1<Scanner, String>{
		Scanner scanner = null;

		@Override
		protected void create(List<String> list, Scanner p1) {
			scanner = p1;
		}

		public String getNext() {
			String s = null;
			// not using hasnextline as its painfully slow bug: 5067317
			try {
				s = scanner.nextLine();
			} catch(NoSuchElementException e) {
				s = null; // redunant
			}
			
			if (s == null) {
				EOL();
				return null;
			} else return s;
		}
	};	

	public static final ListFun1<Scanner, String> readSLines = new ReadSLines();

	public static final class InputStreamToBufferedReader extends Fun2<Charset, InputStream, BufferedReader> {
		@Override
		public BufferedReader apply(Charset p1, InputStream p2) {
			return new BufferedReader(new InputStreamReader( p2, p1) );
		}
	}
	
	/**
	 * Splits on line boundaries from a byte buffer in the specified encoding
	 * 
	 * NOTE This class is not memoized by default.
	 * 
	 * @param chunk_size specifies the approx size of byte buffers to use
	 * 
	 * code approach borrowed from unintentionalobjectretention's blog
	 * 
	 * readStrings chunk_size ByteBuffer CharsetDecoder
	 * 
	 */
	public static final class ReadLineBlocks extends ListFun2<Integer, ByteBuffer, InputStream>{
		ByteBuffer orig = null;
		
		int sectionSize = 100;
		
		int numberSections = 0;
		int currentSection = 1;
		int lastPos = 0;
		
		boolean end = false;
		
		@Override
		protected void create(List<InputStream> list, Integer i, ByteBuffer p1) {
			orig = p1;
			sectionSize = i;
			setMemoized(false);
		}

		public InputStream getNext() {
			if (end) {
				EOL();
				return null;
			}
			int skipTo = java.lang.Math.max(currentSection * sectionSize, lastPos + 1);
			int startPos = lastPos + 1;
			currentSection ++;
			
			ByteBuffer buf = orig;
			int i = 0;
			if (skipTo > buf.limit()) {
				// already eol, remaining is less than the sectionSize
				end = true;
			} else {
				buf.position(skipTo);
			    do {
			    	try{
			    		i = buf.get();
			    	} catch (BufferUnderflowException e) {
			    		// no more space, return what we have but don't let any more
				    	end = true;
				    	break;
			    	}
	                skipTo++;
	            } while (i != '\n' && i != -1);
			}
		    
		    lastPos = skipTo;
	        
			return new LimitedLengthInputStream( Channels.newInputStream(new ByteBufferToReadableByteChannel(
					(ByteBuffer) buf.duplicate().position(startPos) ) ), lastPos - startPos );
		}
		
	};	

	/** defaults to 10485760 bytes est per chunk, 10mb, less makes no sense on light processing */
	public static final Fun1<ByteBuffer, Iterable<InputStream> > readLineBlocks = new ReadLineBlocks().apply(10485760);

	public static final Fun1<ByteBuffer, Iterable<InputStream> > readLineBlocks( int size ){
		return new ReadLineBlocks().apply(size);
	}
	
	/**
	 * Greps the second parameter to see if the regex string matches, returning true for a match.
	 * 
	 * grep String Object
	 * 
	 */
	public static class Grep<T> extends Fun2<String, T, Boolean>{
		@Override
		public Boolean apply(String p1, T p2) {
			if (p1==null || p2==null) return false;
			
			return p2.toString().matches(p1);
		}
	};
	
	public static <T> Fun1<T, Boolean> grepWith(String p1) {
		return new Grep<T>().apply(p1);
	}
	
	
	/**
	 * Greps the object (toString) to see if the Pattern matches, returning true for a match.
	 * 
	 * grepP Pattern Object
	 * 
	 */
	public static class GrepP<T> extends Fun2<Pattern, T, Boolean>{
		@Override
		public Boolean apply(Pattern p1, T p2) {
			if (p1==null || p2==null) return false;
			
			String string = p2.toString();
			
			return p1.matcher(string).matches();
		}
	};	
	
	public static <T> Fun1<T, Boolean> grepWith(Pattern p1) {
		return new GrepP<T>().apply(p1);
	}
	
	/**
	 * Returns a matching predicate for string equality
	 * @param cmp
	 * @return
	 */
	public static final Fun1<String, Boolean> eq(final String cmp){
		return new Fun1<String, Boolean> (){
			@Override
			public Boolean apply(String to) {
				if (cmp==null && to!=null) return false;
				if (cmp!=null && to==null) return false;
				
				if (cmp == to) return true;
				if (cmp.equals(to)) return true;
				
				return false;
			}
		};
	}

	/**
	 * Returns a matching predicate for string case insensitive equality
	 * @param cmp
	 * @return
	 */
	public static final Fun1<String, Boolean> eqi(final String cmp){
		return new Fun1<String, Boolean> (){
			@Override
			public Boolean apply(String to) {
				if (cmp==null && to!=null) return false;
				if (cmp!=null && to==null) return false;
				
				if (cmp == to) return true;
				if (cmp.equalsIgnoreCase(to)) return true;
				
				return false;
			}
		};
	}

	/**
	 * Returns a matching predicate for string regex matches
	 * @param cmp
	 * @return
	 */
	public static final Fun1<String, Boolean> eqr(final String cmp){
		return new Fun1<String, Boolean> (){
			@Override
			public Boolean apply(String to) {
				if (cmp==null && to!=null) return false;
				if (cmp!=null && to==null) return false;
				
				if (cmp == to) return true;
				if (to.matches(cmp)) return true;
				
				return false;
			}
		};
	}
	
	/**
	 * Calls toString on the object and write's it to the Writer.
	 * 
	 * The optional string is for adding suffixes to the output, like commas or newlines
	 * 
	 * NOTE (if Object is null then "null" will be written.
	 * 
	 * print Writer Object [String]
	 */
	public static class Print<T> extends Fun3<Writer, T, String, T>{
		
		@Override
		public T apply(Writer writer, T ol1, String suffix) {
			try {	
				if (ol1 != null) {
					writer.write(ol1.toString());
				} else {
					writer.write("null");
				}
				if (suffix != null) {
					writer.write(suffix);
				}
			} catch (Exception e){
				throw new ApplyException("Could not print ", e);
			}
			return null;
		}
	};
	
	public static final <T> Fun1<T, T> print(Writer w){
		return new Print<T>().curry(w,_, (String)null);
	}
	
	/**
	 * Writes objects with a newline
	 * 
	 * println Writer Object
	 */
	public static final <T> Fun1<T, T> println(Writer w){
		return new Print<T>().curry(w,_, NewLine);
	}
	
	/**
	 * Returns true if the two arguments are equals using toString.
	 * 
	 * returns false if only one of the arguments is null.
	 */
	public static class StrEquals<A,B> extends Fun2<A, B, Boolean>{
		@Override
		public Boolean apply(A p1, B p2) {
			if (p1!=null) {
				if (!p1.toString().equals(p2.toString()) )  {
					return false;
				}
			} else if (p2 !=null) {
				// 0 is null but 1 isn'ts is returning false
				return false;
			}
			return true;
		}
	};
	public static final <A,B> Boolean strEquals(A a , B b) {
		return new StrEquals<A,B>().apply(a,b);
	}
	

	/**
	 * Returns the value of the argument
	 * 
	 * value object
	 */
	public static class ToString<T> extends Fun1<T,String> {
		@Override
		public String apply(T p1) {
			return p1.toString();
		}
	};
	
	/**
	 * ToStrings provides a list of toString for every item in its list
	 * 
	 * toStrings iterable
	 * 
	 * @param <T>
	 */
	public static class ToStrings<T> extends ListFun1<Iterable<T>,String> {
		Iterator<T> itr = null;
		@Override
		protected void create(List<String> list, Iterable<T> p1) {
			itr = p1.iterator();
		}

		public String getNext() {
			if (itr.hasNext()) {
				return itr.next().toString();
			} else {
				EOL();
				return null;
			}
		}
	};
	
	public static final <T> Iterable<String> toStrings(Iterable<T> list){
		return new ToStrings<T>().apply(list);
	}
	
	public static final Fun2<Iterable<String>, String, String> string = new Fun2<Iterable<String>, String, String>() {
		@Override
		public String apply(Iterable<String> p1, String seperator) {
			Iterator<String> itr = p1.iterator();
			StringBuilder builder = new StringBuilder();
			while (itr.hasNext()) {
				builder.append(itr.next());
				builder.append(seperator);
			}
			return builder.toString();
		}
	};
	
	public static final Fun1<Iterable<String>, String> unlines = string.curry(_,NewLine);
	public static final Fun1<Iterable<String>, String> join = string.curry(_,"");
		
	public static final Fun1<String, String> reverse = new Fun1<String, String> () {
		@Override
		public String apply(String p1) {
			StringBuilder builder = new StringBuilder(p1);
			return builder.reverse().toString();
		}
	};
	
	public static final class Chars extends ListFun1<String, Character>{

		String input = null;
		
		@Override
		protected void create(List<Character> list, String p1) {
			input = p1;
			length = input.length();
		}

		int pos = 0;
		int length = 0;
		
		// add em as we go
		public Character getNext() {
			if (pos<length){
				return input.charAt(pos++);
			}
			
			EOL();
			return null;
		}
		
	};
	
	public static final ListFun1<String, Character> chars = new Chars();
	
	/**
	 * Splits on the seperator
	 */
	public static final Fun2<String, String, Iterable<String>> splitOn = new Fun2< String, String, Iterable<String>>() {
		@Override
		public Iterable<String> apply(String p1, String seperator) {
			ArrayList<String> strings = new ArrayList<String>();
			String[] bits = p1.split(seperator);
			Collections.addAll(strings, bits);
			return strings;
		}
	};
	
	/**
	 * Splits on whitespace
	 */
	public static final Fun1<String, Iterable<String>> split = splitOn.curry(_,"\\s");
	
	/**
	 * Comparator for strings, returns the first param if the strings are equal
	 */
	public static final Fun2<String, String, String> stringCmp = new Fun2<String, String, String>(){
		@Override
		public String apply(String p1, String p2) {
			int res = p1.compareTo(p2);
			if (res == 0) return p1;
			if (res < 0) return p2;
			return p1;
		}
		
	};
}
